Using Read/Write Commands
The commands provided by the Read/Write Commands scripting addition allow you to open a file for access, get and set its length, read data from the file, insert new data in the file, and close access to the file. These commands allow you to make use, from within a script, of some capabilities of the File Manager, the part of the Macintosh Operating System that controls files.
Most of the Read/Write Commands allow you to specify a file reference number instead of a reference to a file. A file reference number is an integer, assigned by the File Manager, that uniquely identifies a file. You can obtain a file reference number with the Open for Access command, then use the number returned to refer to the same file until you use the Close Access command to close the file. It is usually preferable to specify a file reference number rather than a reference to a file because it takes the Read/Write Commands scripting addition less time to locate the file.
- WARNING
- The Read/Write Commands scripting addition is intended for use by experienced programmers. If you are not familiar with the File Manager as described in Inside Macintosh: Files, proceed with caution. Using these commands incorrectly may cause loss of data.<8batcolor>s
![]()
You can use the Read/Write commands with either text-based data or binary data. Most databases can export data as text, with fields and records separated by delimiters, and some store their data as text files. The examples in this section demonstrate how to use the Read/Write commands with text-based data. These examples assume that you have basic information about the way the data is stored in a text file, such as the delimiters used to separate fields and records. You can use similar techniques to read and write binary data if you know how the data is organized within a file. For example, if you know the header format for a file of type
'PICT'
, you can write scripts that read and write to'PICT'
files.Both the Read command and the Write command make use of the file mark, a marker used by the File Manager that indicates the byte at which the Read and Write commands begin operating. By default, the file mark is the first byte of the file. After the Read command reads a range of bytes or the Write command writes over a range of bytes, the file mark is set to byte just after the end of that range. The next Read or Write command begins operating at the new file mark.
For example, suppose you want to extract a particular record from a text-based database of names and addresses. To do so, you need to know the number of fields in each record, the position of the desired record in the database, and the delimiters used to separate the records in the database. You can then use the Open for Access command to get a file reference number for the file that contains the desired record and a Read command within a repeat loop to read each successive record. After reading each record, the Read command sets the file mark to the beginning of the next record. When the repeat loop determines that the desired record has been reached, it returns the data for that record. Listing 2-1 shows one way to do this.
Listing 2-1 Reading a specific record from a text-based database file
--first choose data file to work with set pathToUse to choose file try set x to open for access pathToUse set z to ReadRecord(10, 1, tab, return, x) close access x z --display requested record on error errString number errNum display dialog errString close access x end try on ReadRecord(numberOfFields, whichRecord, fieldDelimiter, ¬ recordDelimiter, fileRefNum) try (* if there's a record delimiter, read all fields except for last using field delimiter, then read last field using record delimiter *) if recordDelimiter is "" then set readxTimes to numberOfFields else set readxTimes to numberOfFields - 1 end if repeat whichRecord times set recordData to {} repeat (readxTimes) times set recordData to recordData & ¬ {(read fileRefNum before fieldDelimiter)} end repeat if readxTimes is not numberOfFields then set recordData to recordData & ¬ {(read fileRefNum before recordDelimiter)} end if end repeat return recordData on error errString number errNum display dialog errString return errString end try end ReadRecordThe script in Listing 2-1 begins by using the Choose File command to allow
the user to choose the text file that contains the desired record. After initializing the variable into which the record will be read, the script uses the Open
for Access command to open the file and theReadRecord
handler to read a specific record.The
ReadRecord
handler shown in Listing 2-1 takes five parameters:
If recordDelimiter is set to
- numberOfFields
- The number of fields in the record.
- whichRecord
- An integer that identifies the position of the desired record.
- fieldDelimiter
- The delimiter used in the file to separate fields.
- recordDelimiter
- The delimiter (if any) used to separate records. If the file doesn't use a different delimiter to separate records, this parameter must be set to
""
.- fileRefNum
- A file reference number obtained with the Open for Access command.
""
, theReadRecord
handler reads the specified number of fields for each record. If recordDelimiter is set to a delimiter,ReadRecord
reads all the fields in a record but the last, then reads the last field up to the record delimiter. This is necessary to ensure that the last field of one record is not combined with the first field of the next.The
ReadRecord
handler reads each new record into the variablerecordData
. If the record is the one requested,ReadRecord
returns
that record. If the record is not the requested record,ReadRecord
setsrecordData
to an empty list and reads the next record.You can use similar techniques to locate the exact position of a record you want to delete from a text file. In addition to locating the record to be deleted, you need to store all the records after that record in a variable and write the contents of the variable starting at the beginning of the record to be deleted. You can then use the Get EOF and Set EOF commands to get the initial size of the file and reset its size after deleting the record. Listing 2-2 demonstrates how to do this.
Listing 2-2 Deleting a record from a text-based database file
--choose data file to use set pathToUse to choose file try set x to open for access pathToUse with write permission DeleteRecord(10, 1, tab, return, x) close access x on error errString number errNum display dialog errString close access x end try on DeleteRecord(numberOfFields, whichRecord, ¬ fieldDelimiter, recordDelimiter, fileRefNum) try --initialize variables set startSize to get eof fileRefNum --current size set idx to 1 --counter set preRecordSize to 1 --offset of record to delete set accumulatedSize to 0 --total size of records read if recordDelimiter is "" then set readxTimes to numberOfFields else set readxTimes to numberOfFields - 1 end if repeat with idx from 1 to whichRecord repeat (readxTimes) times set q to read fileRefNum until fieldDelimiter set accumulatedSize to accumulatedSize + (length of q) end repeat if readxTimes is not numberOfFields then set q to read fileRefNum until recordDelimiter set accumulatedSize to accumulatedSize + (length of q) end if (* if record to delete is the first record in file or the next record that will be read, set preRecordSize *) if whichRecord is 1 or idx is whichRecord - 1 then if whichRecord is 1 then set preRecordSize to 1 else set preRecordSize to accumulatedSize end if end if end repeat (* now that preRecordSize is determined, read the record to be deleted so file mark is set to beginning of next record *) set fileBuffer to read fileRefNum from accumulatedSize + 1 --next, overwrite record to be deleted with remainder of file if (startSize - accumulatedSize) is not 0 then write fileBuffer to fileRefNum starting at preRecordSize set eof fileRefNum to (startSize - accumulatedSize) else (* if the file contains only the record to be deleted, set the end of the file to 0 *) if whichRecord is 1 then set eof fileRefNum to 0 (* if record to be deleted is last record in file, just shrink the file *) else set eof fileRefNum to preRecordSize end if end if on error errString number errNum display dialog errString end try end DeleteRecordTheDeleteRecord
handler shown in Listing 2-2 takes five parameters:
Like the
- numberOfFields
- The number of fields in each record.
- whichRecord
- An integer that identifies the position of the record you want
to delete.- fieldDelimiter
- The delimiter used in the file to separate fields.
- recordDelimiter
- The delimiter (if any) used to separate records. If the file doesn't use a different delimiter to separate records, this parameter must be set to
""
.- fileRefNum
- A file reference number obtained with the Open for Access command.
ReadRecord
handler in Listing 2-1, theDeleteRecord
handler reads the specified number of fields for each record if recordDelimiter is set to""
. If recordDelimiter is set to a delimiter,DeleteRecord
reads all the fields in a record but the last, then reads the last field up to the record delimiter. The size of each successive record is added to theaccumulatedSize
variable, which contains the total size of the previously read records.When it reaches the record to be deleted,
DeleteRecord
stores the contents ofaccumulatedSize
in thepreRecordSize
variable, reads through the record to set the file mark, reads from the file mark to the end of the file, and stores that portion of the file in thefileBuffer
variable. Finally,DeleteRecord
writes the contents offileBuffer
starting at the beginning of the record to
be deleted.Listing 2-3 demonstrates how you can use similar techniques to insert a record into a text-based database file.
Listing 2-3 Inserting a record in a database file
--choose file to work with set pathToUse to choose file try (* first put the record to be added into a variable; in this case the record to be added is actually an AppleScript list because the file on disk doesn't include label data *) set newRecord to ¨ {"Granny", "Smith", "123 Potato Chip Lane", ¬ "Palo Minnow", "CA", "98761", "Snackable Computer", ¬ "888-987-0987", "978 -234-5432", "123-985-1122"} set x to open for access pathToUse with write permission AddRecord(newRecord, 5, tab, return, x) close access x on error errString number errNum display dialog errString close access x end try on AddRecord(recordToAdd, addWhere, fieldDelimiter, ¬ recordDelimiter, fileRefNum) try --initialize variables set idx to 1 --counter set preRecordSize to 1 --offset of byte at which to add file set accumulatedSize to 0 --total size of records read set numberOfFields to count of recordToAdd if recordDelimiter is "" then set readxTimes to numberOfFields else set readxTimes to numberOfFields - 1 end if (* if the record is to be added at the beginning of the file, this If statement adds the record *) if addWhere is 1 then --read from beginning of file and store in postBuffer set postBuffer to read fileRefNum from 1 (* before writing new record, file mark must be reset to beginning of file; to do this, write an empty string to the beginning of file *) write "" to fileRefNum starting at 0 WriteNewRecord(recordToAdd, fieldDelimiter, ¬ recordDelimiter, fileRefNum) --now add back the rest of the record write postBuffer to fileRefNum return end if (* if the record is to be added somewhere other than at the beginning of the file, the rest of the AddRecord handler is executed *) repeat with idx from 1 to addWhere - 1 repeat (readxTimes) times set q to read fileRefNum until fieldDelimiter set accumulatedSize to accumulatedSize + (length of q) end repeat if readxTimes is not numberOfFields then set q to read fileRefNum until recordDelimiter set accumulatedSize to accumulatedSize + (length of q) end if end repeat (* read from beginning of file to the byte at which the new record is to be added *) set postBuffer to read fileRefNum from accumulatedSize + 1 (* before writing new record, set file mark to byte at which new record is to be added; to do this, write an empty string to that byte *) write "" to fileRefNum starting at accumulatedSize + 1 WriteNewRecord(recordToAdd, fieldDelimiter, recordDelimiter, ¬ fileRefNum) --now add back the rest of the record write postBuffer to fileRefNum on error errString number errNum display dialog errString end try end AddRecord on WriteNewRecord(recordToAdd, fieldDelimiter, recordDelimiter,¬ fileRefNum) try set numberOfFields to count of recordToAdd if recordDelimiter is "" then set readxTimes to numberOfFields else set readxTimes to numberOfFields - 1 end if repeat with idx from 1 to numberOfFields if idx æ readxTimes then write item idx of recordToAdd & fieldDelimiter to ¬ fileRefNum else (* if file uses a record delimiter, write delimiter after the last field in the record *) write item idx of recordToAdd & recordDelimiter to ¬ fileRefNum end if end repeat on error errString number errNum display dialog errString end try end WriteNewRecordTheAddRecord
handler shown in Listing 2-3 takes five parameters:
If the new record is to be added at the beginning of the file, AddRecord reads all the records in the file and stores them in the
- recordToAdd
- A list of the fields for the record to be added.
- whichRecord
- An integer that identifies the offset of the record you want
to add.- fieldDelimiter
- The delimiter used in the file to separate fields.
- recordDelimiter
- The delimiter (if any) used to separate records. If the file doesn't use a different delimiter to separate records, this parameter must be set to
""
.- fileRefNum
- A file reference number obtained with the Open for Access command.
postBuffer
variable, then resets the file mark to the beginning of the file by writing an empty string to that location. This is a useful technique whenever you want to set the file mark without reading or writing any data.Next,
AddRecord
uses theWriteNewRecord
handler to write the record at the beginning of the file and writes the contents of thepostBuffer
variable after the new record. Note that the Write Command sets the end of file, so this example doesn't need to use the Get EOF and Set EOF commands.If the new record is to be added somewhere other than at the beginning of the file,
AddRecord
uses a repeat loop to read through all the records that precede the new record's location. If recordDelimiter is set to""
,AddRecord
reads the specified number of fields for each record. If recordDelimiter is set to a delimiter,AddRecord
reads all the fields in a record but the last, then reads the last field up to the record delimiter. The size of each successive record is added to theaccumulatedSize
variable, which contains the total size of the previously read records.After it has stored, in the
accumulatedSize
variable, the total size of the records preceding the point at which the new record is to be added,AddRecord
reads the remainder of the file and stores it in thepostBuffer
variable. It then resets the file mark to the byte at which the new record is to be added by writing an empty string to that location. After using theWriteNewRecord
handler to write the record,AddRecord
writes the contents of thepostBuffer
variable after the new record.The
WriteNewRecord
handler shown in Listing 2-3 takes four parameters:
If recordDelimiter is set to
- recordToAdd
- A list of the fields for the record to be added.
- fieldDelimiter
- The delimiter used in the file to separate fields.
- recordDelimiter
- The delimiter (if any) used to separate records. If the file doesn't use a different delimiter to separate records, this parameter must be set to
""
.- fileRefNum
- A file reference number obtained with the Open for Access command.
""
,WriteNewRecord
includes a field delimiter after each field it writes. If recordDelimiter is set to a delimiter,WriteNewRecord
includes a field delimiter after each field in a record but
the last and includes a record delimiter after the last field.Listing 2-4 demonstrates one way to take advantage of the fact that the Open for Access command can create a file with a specified name in a specified location if the file doesn't already exist at that location.
Listing 2-4 Opening a file for write access and creating one if the file doesn't exist
on OpenFileIfItExists(theFile, writePermission) try (* if theFile doesn't exist, Info For returns error -43 *) set x to info for file theFile if writePermission is true then return (open for access file theFile with write permission) else return (open for access file theFile) end if on error theErrMsg number errorNum try --if error is -43, the user can choose to create the file display dialog "The file: " & theFile & " does not exist" ¬ buttons {"Create It For Me", "Cancel", "Ok"} ¬ default button 2 if button returned of the result is "Ok" then return errorNum else --create the file if writePermission is true then return open for access file theFile ¬ with write permission else return open for access file theFile end if end if on error theErrMsg number theErrNumber return theErrNumber end try end try end OpenFileIfItExists --set a variable to the file you want to open or create set fileToOpenOrCreate to "Hard Disk:Test File One" set z to OpenFileIfItExists(fileToOpenOrCreate, true) if z < 0 then --OpenFileIfItExists returned an error display dialog the result else --OpenFileIfItExists returned a file reference number --do your work with the open file here close access z end ifTheOpenFileIfItExists
handler shown in Listing 2-4 takes two parameters:
To determine whether the file exists or not,
- theFile
- A string that consists of the full pathname for the file to open
or create.- writePermission
- A Boolean value that indicates whether to open the file with (
true
) or without (false
) write permission.OpenFileIfItExists
uses the Info For command. If the file doesn't exist, the Info For command returns
error -43, "File wasn't found," andOpenFileIfItExists
displays a dialog box that allows the user to choose whether to create the new file. If the file exists or if it is successfully created,OpenFileIfItExists
opens it with or without write permission, depending on the value of the writePermission parameter.